package ai;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

import ai.masks.Mask;
import ai.masks.Slot2DMask1;
import ai.masks.Slot2DMask2;
import ai.masks.Slot2DMask3;
import ai.masks.Slot2DMask4;
import ai.masks.Slot2HMask1;
import ai.masks.Slot2HMask2;
import ai.masks.Slot2VMask;
import ai.masks.Slot3DMask1;
import ai.masks.Slot3DMask2;
import ai.masks.Slot3DMask3;
import ai.masks.Slot3DMask4;
import ai.masks.Slot3HMask1;
import ai.masks.Slot3HMask2;
import ai.masks.Slot3VMask;
import model.AbstractPlayer;
import model.Slot;
import model.State;

public abstract class AIPlayer extends AbstractPlayer {
	protected int maxDepth;
	protected boolean enDepthCheck, enReordering;
	protected BestChoiceType choiceType;
	protected List<HeuristicType> heuristics;

	private List<Mask> slot3Masks;
	private List<Mask> slot2Masks;

	public AIPlayer(Color color, String description, int maxDepth,
			boolean enDepthCheck, BestChoiceType choiceType,
			boolean enReordering, List<HeuristicType> heuristics) {
		super(color, description);
		this.maxDepth = maxDepth;
		this.slot3Masks = createSlot3MaskList();
		this.slot2Masks = createSlot2MaskList();
		this.enDepthCheck = enDepthCheck;
		this.enReordering = enReordering;
		this.choiceType = choiceType;
		this.heuristics = heuristics;
	}

	public abstract int executeMove();

	private ArrayList<Integer> getBestActionFromValues(
			ArrayList<Integer> actions, ArrayList<NodeValue> returnValues) {

		// Ricerco le mosse con i migliori valori di utilit??
		ArrayList<Integer> maxIndexes = new ArrayList<Integer>();
		maxIndexes.add(0);
//		System.out.println("nuova mossa " + getPlayerSlot().toString());
		float maxVal = returnValues.get(0).getValue();
//		System.out.println(actions.get(0) + " Value: "
//				+ returnValues.get(0).getValue() + " Depth: "
//				+ returnValues.get(0).getDepth());
		for (int i = 1; i < returnValues.size(); i++) {
			if (returnValues.get(i).getValue() > maxVal) {
				maxVal = returnValues.get(i).getValue();
				maxIndexes.clear();
				maxIndexes.add(i);
			} else if (returnValues.get(i).getValue() == maxVal) {
				maxIndexes.add(i);
			}
//			System.out.println(actions.get(i) + " Value: "
//					+ returnValues.get(i).getValue() + " Depth: "
//					+ returnValues.get(i).getDepth());
		}

		/**************************************************************
		 * DEPTH CHECK *
		 **************************************************************/
		if (enDepthCheck && (maxVal==1 || maxVal == 0)) {
			// Tra le mosse col migliore valore di utilit?? ricerco le mosse
			// col migliore valore di profondit??
			ArrayList<Integer> maxIndexesDepth = new ArrayList<Integer>();
			maxIndexesDepth.add(maxIndexes.get(0));
			int actualDepth = returnValues.get((maxIndexes.get(0))).getDepth();
			// Se l'utilit?? indica una vittoria certa scelgo la mossa pi??
			// breve
			if (maxVal == 1) {
				for (int i = 1; i < maxIndexes.size(); i++) {
					int depth = returnValues.get((maxIndexes.get(i)))
							.getDepth();
					if (depth < actualDepth) {
						actualDepth = depth;
						maxIndexesDepth.clear();
						maxIndexesDepth.add(maxIndexes.get(i));
					} else if (depth == actualDepth) {
						maxIndexesDepth.add(maxIndexes.get(i));
					}
				}

			}
			// Se l'utilit?? indica una sconfitta certa scelgo la mossa pi?? lunga 
			// sperando in un avversario n
			else if (maxVal == 0){
				for (int i = 1; i < maxIndexes.size(); i++) {
					int depth = returnValues.get((maxIndexes.get(i)))
							.getDepth();
					if (depth > actualDepth) {
						actualDepth = depth;
						maxIndexesDepth.clear();
						maxIndexesDepth.add(maxIndexes.get(i));
					} else if (depth == actualDepth) {
						maxIndexesDepth.add(maxIndexes.get(i));
					}
				}
			}

			return maxIndexesDepth;
		}

		return maxIndexes;

	}

	protected int getRandomBestActionFromValues(ArrayList<Integer> actions,
			ArrayList<NodeValue> returnValues) {
		ArrayList<Integer> maxIndexes = getBestActionFromValues(actions,
				returnValues);

		int randomMaxMove = actions
				.get(maxIndexes.get((int) (Math.random() * maxIndexes.size())));

//		System.out.println("Mossa scelta: " + randomMaxMove);
		return randomMaxMove;
	}

	protected int getCenteredShortestBestActionFromValues(
			ArrayList<Integer> actions, ArrayList<NodeValue> returnValues) {
		ArrayList<Integer> maxIndexes = getBestActionFromValues(actions,
				returnValues);

		int centerMaxMove;
		if (enReordering)
			centerMaxMove = actions.get(maxIndexes.get(0));
		else
			centerMaxMove = actions.get(maxIndexes.get(maxIndexes.size() / 2));

//		System.out.println("Mossa scelta: " + centerMaxMove);
		return centerMaxMove;
	}

	protected int getGaussianBestActionFromValues(ArrayList<Integer> actions,
			ArrayList<NodeValue> returnValues) {

		ArrayList<Integer> maxIndexes = getBestActionFromValues(actions,
				returnValues);

		Random randomGenerator = new Random();
		double standardDev = 0.25 * maxIndexes.size();
		double gaussianIndex;
		if (enReordering) {
			// gaussiana con media sulla colonna centrale e deviazione standard
			// maxIndexes.size()/4
			// 70% dei valori a distanza maxIndexes.size()/4 dalla media, 99% a
			// (3/4)maxIndexes.size().
			// In questo caso per?? la colonna centrale si trova in indice 0.
			gaussianIndex = Math.abs(randomGenerator.nextGaussian()
					* standardDev);

		} else {
			// gaussiana con media sulla colonna centrale e deviazione standard
			// maxIndexes.size()/4
			// 70% dei valori a distanza maxIndexes.size()/4 dalla media, 99% a
			// (3/4)maxIndexes.size()
			gaussianIndex = randomGenerator.nextGaussian() * standardDev
					+ maxIndexes.size() / 2;

		}

		// controllo valori limite
		if (gaussianIndex < 0)
			gaussianIndex = 0;
		if (gaussianIndex >= maxIndexes.size())
			gaussianIndex = maxIndexes.size() - 1;
//		System.out.println("Size: " + maxIndexes.size() + " SelectedIndex: "
//				+ (int) gaussianIndex);
		return actions.get(maxIndexes.get((int) gaussianIndex));
	}

	protected ArrayList<Integer> getActions(Slot[][] boardState) {
		ArrayList<Integer> actions = new ArrayList<Integer>();
		for (int i = 0; i < boardState[0].length; i++) {
			if (positionIsValid(i, boardState)) {
				actions.add(i);
			}
		}

		/**************************************************************
		 * REORDERING ACTIONS *
		 **************************************************************/
		if (enReordering)
			return reorderAction(actions);
		else
			return actions;

	}

	/**
	 * Riordina le azioni in modo da analizzare per primo i successori pi??
	 * promettenti. In questo modo aumenta l'efficacia della potatura
	 * dell'algoritmo ABPruning aumenta.
	 * 
	 * @param orderedActions
	 *            Lista di azioni ordinate in modo crescente dalla colonna 0
	 *            alla N
	 * @return La lista di azioni riordinate
	 */
	private ArrayList<Integer> reorderAction(ArrayList<Integer> orderedActions) {
		ArrayList<Integer> reordered = new ArrayList<Integer>();
		int dim = orderedActions.size();
		for (int i = 0; i < dim; i++) {
			reordered.add(orderedActions.remove(orderedActions.size() / 2));
		}
		return reordered;
	}

	protected Slot[][] result(Slot[][] boardState, int col, Slot s) {
		Slot[][] copy = boardState.clone();
		for (int i = 0; i < boardState.length; i++) {
			copy[i] = boardState[i].clone();
		}
		int freeRow = getFreeRow(copy, col);
		// if(freeRow < copy.length)
		copy[freeRow][col] = s;

		return copy;
	}

	protected int getFreeRow(Slot[][] boardState, int col) {
		int i = boardState.length - 1;
		while (i >= 0 && !boardState[i][col].equals(Slot.FREE)) {
			i--;
		}
		return i;
	}

	protected float getUtility(State s, Slot[][] board, Slot playerTurn) {
		float utility;
		switch (s) {
		case DRAW:
			utility = 0.5f;
			break;
		case VICTORY_1:
			if (getPlayerSlot() == Slot.PLAYER1)
				utility = 1;
			else
				utility = 0;
			break;
		case VICTORY_2:
			if (getPlayerSlot() == Slot.PLAYER2)
				utility = 1;
			else
				utility = 0;
			break;
		default:
			/**************************************************************
			 * ENABLE HEURISTICS *
			 **************************************************************/
			if (heuristics.size() > 0)
				utility = getHeuristicValue(board, playerTurn);
			else
				utility = 0.2F; // VALORE DI DEFAULT DA USARE SENZA EURISTICHE

		}

		return utility;
	}

	private float getHeuristicValue(Slot[][] board, Slot playerTurn) {
		ArrayList<Threat> threats = findThreats(board);
		ArrayList<Threat> potentialThreats = findPotentialThreats(board);
		// DEBUG
		// if (!threats.isEmpty()) {
		// for (int i = 0; i < board.length; i++) {
		// for (int j = 0; j < board[0].length; j++) {
		// Slot s = board[i][j];
		// if (s == Slot.PLAYER1)
		// System.out.print("R ");
		// else if (s == Slot.PLAYER2)
		// System.out.print("Y ");
		// else
		// System.out.print("- ");
		// }
		// System.out.println();
		// }
		// System.out.println();
		// for (Threat t : threats)
		// System.out.println("Row: " + t.getRow() + " Col: " + t.getCol()
		// + " Player: " + t.getSlot().toString());
		// }

		/**************************************************************
		 * HEURISTICS *
		 **************************************************************/
		float threatFinderValue = 0f;
		float potentialThreatValue = 0f;
		if (heuristics.contains(HeuristicType.THREATFINDER))
			threatFinderValue = threatFinderHeuristic(threats, board,
					playerTurn);
		if (heuristics.contains(HeuristicType.POTENTIALTHREAT))
			potentialThreatValue = potentialThreatHeuristic(potentialThreats);
		if (!heuristics.contains(HeuristicType.THREATFINDER)
				&& !heuristics.contains(HeuristicType.DOUBLETHREAT))
			return 0.2F;

		if (threatFinderValue == 0f)
			return potentialThreatValue;
		if (potentialThreatValue == 0f)
			return threatFinderValue;

		// return getDynamicWeightHeuristic(threatFinderValue,
		// potentialThreatValue);
		return 0.8f * threatFinderValue + 0.2f * potentialThreatValue;
	}

	// private float getDynamicWeightHeuristic(float threatFinderValue,
	// float potentialThreatValue) {
	// if (getPlayerSlot() == Slot.PLAYER1)
	// return 0.6f * threatFinderValue + 0.4f * potentialThreatValue;
	// else
	// return 0.7f * threatFinderValue + 0.3f * potentialThreatValue;
	// }

	/**
	 * Euristica per la ricerca delle minacce. Si basa su un algoritmo per il
	 * riconoscimento delle minacce pari-dispari.
	 * 
	 * Player 1 threat dispari - Player2 threat pari -> vittoria Player1
	 * 
	 * Player 1 threat pari - Player2 threat pari -> vittoria Player 2
	 * 
	 * Player 1 threat pari - Player2 threat dispari -> draw
	 * 
	 * Player 1 threat dispari - Player2 threat dispari -> draw
	 * 
	 * Se vi sono pi?? threat sulla stessa colonna vince la minaccia sulla riga
	 * pi?? in basso
	 * 
	 * @param threats
	 * @return
	 */
	private float threatFinderHeuristic(ArrayList<Threat> threats,
			Slot[][] board, Slot playerTurn) {
		float estimatedVictory = 0.8f;
		float bonusVictory = 0.05f;
		float estimatedDraw = 0.4f;
		float bonusDraw = 0.05f;
		float estimatedDefeat = 0.1f;
		float bonusDefeat = 0.05f;
		float estimatedDoubleThreatVictory = 0.98f;
		float estimatedDoubleThreatDefeat = 0.02f;
		float tacticsVictory = 0.98f;
		float tacticsDefeat = 0.02f;

		int boardRows = board.length;
		int boardCols = board[0].length;

		// Raggruppo i threat per colonne di appartenenza
		ArrayList<TreeSet<Threat>> columnsThreats = new ArrayList<TreeSet<Threat>>();
		for (int i = 0; i < boardCols; i++)
			columnsThreats.add(new TreeSet<Threat>());
		for (Threat threat : threats) {
			columnsThreats.get(threat.getCol()).add(threat);
		}

		// Elimino gli useless threats
		for (TreeSet<Threat> threatColumn : columnsThreats) {
			if (threatColumn.size() > 1) {
				Iterator<Threat> it = threatColumn.iterator();
				Threat first = it.next();
				Threat curr;
				boolean foundFirst = false;
				boolean foundDiff = false;
				while (it.hasNext()) {

					if (!foundFirst) {
						if ((first.isOdd(boardRows)
								&& first.getSlot() == Slot.PLAYER1 && !(playerTurn == Slot.PLAYER2 && first
								.isPlayable()))
								|| (!first.isOdd(boardRows) && first.getSlot() == Slot.PLAYER2)
								&& !(playerTurn == Slot.PLAYER1 && first
										.isPlayable())) {
							foundFirst = true;
						} else
							first = it.next();
					} else {
						curr = it.next();
						if (curr.getSlot() != first.getSlot())
							foundDiff = true;
						if (foundDiff && (curr.getRow() != first.getRow())) {
							it.remove();
						}
					}

				}
			}
		}

		// DEBUG
		// for (TreeSet<Threat> threatColumn : columnsThreats) {
		// if (threatColumn.size() > 1) {
		// for (Threat t : threatColumn) {
		// System.out.println("Row: " + t.getRow() + " Col: "
		// + t.getCol() + " Player: "
		// + t.getSlot().toString());
		// }
		//
		// for (int i = 0; i < board.length; i++) {
		// for (int j = 0; j < board[0].length; j++) {
		// Slot s = board[i][j];
		// if (s == Slot.PLAYER1)
		// System.out.print("R ");
		// else if (s == Slot.PLAYER2)
		// System.out.print("Y ");
		// else
		// System.out.print("- ");
		// }
		// System.out.println();
		// }
		// System.out.println();
		// }
		// }

		/**************************************************************
		 * TACTICS *
		 **************************************************************/
		// Cerco almeno 2 playable threats
		boolean myTactic = false;
		boolean opponentTactic = false;
		int myPlayableThreats = 0;
		int oppPlayableThreats = 0;

		if (heuristics.contains(HeuristicType.TACTICS)) {
			for (TreeSet<Threat> threatColumn : columnsThreats) {
				for (Threat t : threatColumn) {
					if (t.isPlayable()) {
						if (t.getSlot() == getPlayerSlot()) {
							myPlayableThreats++;
						} else {
							oppPlayableThreats++;
						}
					}
				}
			}
		}

		myTactic = myPlayableThreats > 1;
		opponentTactic = oppPlayableThreats > 1;

		if (myTactic && !opponentTactic)
			return tacticsVictory;
		else if (!myTactic && opponentTactic) {
			return tacticsDefeat;
		} else {

			/**************************************************************
			 * ENABLE THREATH BONUS && DOUBLE THREATH *
			 **************************************************************/
			boolean enBonus = heuristics.contains(HeuristicType.THREATBONUS);
			boolean enDouble = heuristics.contains(HeuristicType.DOUBLETHREAT);

			// Classifico i threat
			boolean haveOddThreat = false;
			boolean haveEvenThreat = false;
			int myOddThreats = 0;
			int myEvenThreats = 0;
			boolean hasOppOddThreat = false;
			boolean hasOppEvenThreat = false;
			int oppOddThreats = 0;
			int oppEvenThreats = 0;
			int myLowestThreatIndex = -1;
			int opponentLowestThreatIndex = -1;

			int myPlayableOddThreats = 0;
			int myPlayableEvenThreats = 0;
			int oppPlayableOddThreats = 0;
			int oppPlayableEvenThreats = 0;

//			boolean haveLowerThreat = false;
//			boolean hasOpponentLowerThreat = false;

			Slot me = getPlayerSlot();
			for (TreeSet<Threat> threatColumn : columnsThreats) {
				for (Threat t : threatColumn) {
					if (t.getSlot() == me) {
						// conto threat pari e dispari
						if (t.isOdd(boardRows)) {
							haveOddThreat = true;
							myOddThreats++;
							if (t.isPlayable())
								myPlayableOddThreats++;
						} else {
							haveEvenThreat = true;
							myEvenThreats++;
							if (t.isPlayable())
								myPlayableEvenThreats++;
						}
						// cerco threat pi?? in basso (riga maggiore)
						if (t.getRow() > myLowestThreatIndex)
							myLowestThreatIndex = t.getRow();
					} else {
						if (t.isOdd(boardRows)) {
							hasOppOddThreat = true;
							oppOddThreats++;
							if (t.isPlayable())
								oppPlayableOddThreats++;
						} else {
							hasOppEvenThreat = true;
							oppEvenThreats++;
							if (t.isPlayable())
								oppPlayableEvenThreats++;
						}
						if (t.getRow() > opponentLowestThreatIndex)
							opponentLowestThreatIndex = t.getRow();
					}
				}
			}
//			if (myLowestThreatIndex > opponentLowestThreatIndex)
//				haveLowerThreat = true;
//			else if (myLowestThreatIndex < opponentLowestThreatIndex)
//				hasOpponentLowerThreat = true;

			boolean haveDoubleThreat = false;
			boolean hasOpponentDoubleThreat = false;
			if (enDouble) {
				for (TreeSet<Threat> threatColumn : columnsThreats) {
					// nel caso ci siano almeno 2 threat, verifico che ci siano
					// 2
					// threat
					// dello stesso giocatore nella stessa colonna e su righe
					// consecutive
					if (threatColumn.size() > 1) {

						Iterator<Threat> it = threatColumn.iterator();
						Threat curr = it.next();
						boolean columnHasDoubleThreat = false;
						while (it.hasNext() && !columnHasDoubleThreat) {

							Threat t = it.next();
							if (curr.getSlot() == t.getSlot()
									&& curr.getRow() == (t.getRow() + 1)) {
								columnHasDoubleThreat = true;
								if (getPlayerSlot() == curr.getSlot())
									haveDoubleThreat = true;
								else
									hasOpponentDoubleThreat = true;
							}
							curr = t;
						}

					}
				}
			}

			// if (haveDoubleThreat || hasOpponentDoubleThreat) {
			// // DEBUG
			//
			// for (int i = 0; i < board.length; i++) {
			// for (int j = 0; j < board[0].length; j++) {
			// Slot s = board[i][j];
			// if (s == Slot.PLAYER1)
			// System.out.print("R ");
			// else if (s == Slot.PLAYER2)
			// System.out.print("Y ");
			// else
			// System.out.print("- ");
			// }
			// System.out.println();
			//
			// }
			// System.out.println("\nDOUBLE THREAT");
			// //
			// }

			// DOUBLETHREAT
			if (enDouble) {
				if (haveDoubleThreat && !hasOpponentDoubleThreat)
					return estimatedDoubleThreatVictory;
				else if (hasOpponentDoubleThreat && !haveDoubleThreat) {

					return estimatedDoubleThreatDefeat;
				}
			}

			// Applico l'euristica ODD EVEN.
			float estimatedValue;
			boolean myTurn = getPlayerSlot() == playerTurn;

			if (me == Slot.PLAYER1) {
				// valuto a seconda del turno

				// VITTORIA
				if ((haveOddThreat && !hasOppOddThreat && myTurn)
						|| (!myTurn && haveOddThreat && !hasOppOddThreat && (myOddThreats
								- myPlayableOddThreats > 0))) {
					estimatedValue = estimatedVictory;

					// BONUS
					if (enBonus) {
						if (myOddThreats > 1)
							estimatedValue += 2 * bonusVictory;
						if (myEvenThreats > 0)
							estimatedValue += bonusVictory;
					}
				}

				// SCONFITTA
				else if ((hasOppEvenThreat && !haveOddThreat && !myTurn)
						|| (hasOppEvenThreat && !haveOddThreat && myTurn && (oppEvenThreats - oppPlayableEvenThreats) > 0)) {
					estimatedValue = estimatedDefeat;

					// BONUS
					if (enBonus) {
						if (myEvenThreats > 0)
							estimatedValue += bonusDefeat;
					}
				}

				// PAREGGIO
				else {
					estimatedValue = estimatedDraw;

					// BONUS
					if (enBonus) {
						if (myEvenThreats + myOddThreats > 1)
							estimatedValue += bonusDraw;
						if (myEvenThreats + myOddThreats - oppEvenThreats
								- oppOddThreats < 0)
							estimatedValue -= 2 * bonusDraw;
					}
//					if (haveLowerThreat)
//						estimatedValue += bonusDraw;
//					else if (hasOpponentLowerThreat)
//						estimatedValue -= bonusDraw;

				}

			} else {
				// VITTORIA.
				// almeno uno non giocabile
				// =(myEvenThreats-myPlayableEvenThreats)>0)
				if ((haveEvenThreat && !hasOppOddThreat && myTurn)
						|| (haveEvenThreat && !hasOppOddThreat && !myTurn && (myEvenThreats - myPlayableEvenThreats) > 0)) {
					estimatedValue = estimatedVictory;

					// BONUS
					if (enBonus) {
						if (myEvenThreats > 1)
							estimatedValue += 2 * bonusVictory;
						if (myOddThreats > 0)
							estimatedValue += bonusVictory;
					}
				}

				// SCONFITTA
				else if ((hasOppOddThreat && !haveOddThreat && !myTurn)
						|| (hasOppOddThreat && !haveOddThreat && myTurn && (oppOddThreats - oppPlayableOddThreats) > 0)) {
					estimatedValue = estimatedDefeat;

					// BONUS
					if (enBonus) {
						if (myEvenThreats > 0)
							estimatedValue += bonusDefeat;
					}
				}

				// PAREGGIO
				else {
					estimatedValue = estimatedDraw;

					// BONUS
					if (enBonus) {
						if (myEvenThreats + myOddThreats > 1)
							estimatedValue += bonusDraw;
						if (myEvenThreats + myOddThreats - oppEvenThreats
								- oppOddThreats < 0)
							estimatedValue -= 2 * bonusDraw;
					}
//					if (haveLowerThreat)
//						estimatedValue += bonusDraw;
//					else if (hasOpponentLowerThreat)
//						estimatedValue -= bonusDraw;

				}
			}

			return estimatedValue;
		}

	}

	private float potentialThreatHeuristic(ArrayList<Threat> potentialThreats) {
		int myPotentialThreats = 0;
		int oppPotentialThreats = 0;

		for (Threat threat : potentialThreats) {
			if (threat.getSlot() == getPlayerSlot())
				myPotentialThreats++;
			else
				oppPotentialThreats++;
		}

		float estimatedValue = 0.5f;
		float bonus = 0.1f;
		if (myPotentialThreats > oppPotentialThreats) {
			estimatedValue = 0.6f;
			int diff = myPotentialThreats - oppPotentialThreats;

			estimatedValue += bonus * diff;

			// saturazione
			if (estimatedValue > 0.9)
				estimatedValue = 0.9f;

		} else if (myPotentialThreats < oppPotentialThreats) {
			estimatedValue = 0.4f;
			int diff = oppPotentialThreats - myPotentialThreats;

			estimatedValue -= bonus * diff;

			// saturazione
			if (estimatedValue < 0.1)
				estimatedValue = 0.1f;
		}

		return estimatedValue;
	}

	/**
	 * Calcola i threats presenti nella board
	 * 
	 * @param board
	 * @return lista contenente tutti i threat trovati
	 */
	private ArrayList<Threat> findThreats(Slot[][] board) {
		int row = board.length - 1;
		int col = 0;

		ArrayList<Threat> threats = new ArrayList<Threat>();
		boolean endCol = false;
		while (col < board[0].length) {
			while (row >= 0 && !endCol) {
				if (board[row][col] != Slot.FREE) {
					for (Mask m : slot3Masks) {
						Threat t = m.applyMask(board, row, col);
						if (t != null)
							threats.add(t);
					}
					row--;
				} else {
					endCol = true;
				}
			}
			endCol = false;
			col++;
			row = board.length - 1;
		}
		return threats;
	}

	/**
	 * Calcola i potential threats presenti nella board
	 * 
	 * @param board
	 * @return lista contenente tutti i threat trovati
	 */
	private ArrayList<Threat> findPotentialThreats(Slot[][] board) {
		int row = board.length - 1;
		int col = 0;

		ArrayList<Threat> threats = new ArrayList<Threat>();
		boolean endCol = false;
		while (col < board[0].length) {
			while (row >= 0 && !endCol) {
				if (board[row][col] != Slot.FREE) {
					for (Mask m : slot2Masks) {
						Threat t = m.applyMask(board, row, col);
						if (t != null)
							threats.add(t);
					}
					row--;
				} else {
					endCol = true;
				}
			}
			endCol = false;
			col++;
			row = board.length - 1;
		}
		return threats;
	}

	/**
	 * Crea una lista contenente tutte le maschere disponibili per la ricerca
	 * dei threat su 3 slot
	 * 
	 * @return
	 */
	private List<Mask> createSlot3MaskList() {
		List<Mask> masks = new ArrayList<Mask>();

		masks.add(new Slot3DMask1());
		masks.add(new Slot3DMask2());
		masks.add(new Slot3DMask3());
		masks.add(new Slot3DMask4());
		masks.add(new Slot3HMask1());
		masks.add(new Slot3HMask2());
		masks.add(new Slot3VMask());

		return masks;
	}

	/**
	 * Crea una lista contenente tutte le maschere disponibili per la ricerca
	 * dei threat potenziali su 2 slot
	 * 
	 * @return
	 */
	private List<Mask> createSlot2MaskList() {
		List<Mask> masks = new ArrayList<Mask>();

		masks.add(new Slot2DMask1());
		masks.add(new Slot2DMask2());
		masks.add(new Slot2DMask3());
		masks.add(new Slot2DMask4());
		masks.add(new Slot2HMask1());
		masks.add(new Slot2HMask2());
		masks.add(new Slot2VMask());

		return masks;
	}
}
